﻿using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Provider;
using System.Linq;
using System.Web.Configuration;
using System.Web.Profile;

namespace ADI.Security.Membership
{
    class ADISSOProfileProvider : ProfileProvider
    {
        private string _applicationName;
        private ConnectionStringSettings connectionString;
        private string _providerName;

        public override void Initialize(string name, NameValueCollection config)
        {

            if (config == null)
                throw new ArgumentNullException("config");


            if (name == null || name.Length == 0)
                name = "ADISSOMembershipProvider";

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "AD ITALIA Membership Provider");
            }

            if (String.IsNullOrEmpty(config["applicationName"]))
                throw new ArgumentNullException("applicationName");

            _applicationName = config["applicationName"];

            if (String.IsNullOrEmpty(config["connectionStringName"]))
                throw new ArgumentNullException("connectionStringName");


            Configuration cfg = WebConfigurationManager.OpenWebConfiguration("/");

            connectionString = ConfigurationManager.ConnectionStrings[config["connectionStringName"]];

            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                var appNameExist = adc.RegisteredApp.Where(r => r.AppName.ToLower() == config["applicationName"].ToLower()).SingleOrDefault();
                if (appNameExist == null)
                    throw new Exception(string.Format("L'applicazione {0} non esiste nel database", config["applicationName"]));
            }

            // Initialize base class
            base.Initialize(name, config);

            // This is a non-standard helper setting.
            _providerName = GetConfigValue(config["providerName"], name);
        }

        public override string ApplicationName
        {
            get { return _applicationName; }
            set { _applicationName = value; }
        }

        /*************************************************************************
         * Private helper methods
         *************************************************************************/

        /// <summary>
        /// Used in the initializtion, key in web.config or the default setting if null.
        /// </summary>
        private string GetConfigValue(string configValue, string defaultValue)
        {
            if (String.IsNullOrEmpty(configValue))
                return defaultValue;

            return configValue;
        }

        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection ppc)
        {
            string username = (string)context["UserName"];
            SettingsPropertyValueCollection svc = new SettingsPropertyValueCollection();

            try
            {
                using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                {
                    var profile = adc.Profile.Where(p => p.Member.Username.ToLower() == username.ToLower()).SingleOrDefault();
                    if (profile == null) profile = new Profile();
                    foreach (SettingsProperty prop in ppc)
                    {
                        SettingsPropertyValue pv = new SettingsPropertyValue(prop);

                        switch (prop.Name)
                        {
                            case "Cognome":
                                pv.PropertyValue = profile.Cognome;
                                break;
                            case "Nome":
                                pv.PropertyValue = profile.Nome;
                                break;
                            case "Email":
                                pv.PropertyValue = profile.Email;
                                break;
                            default:
                                throw new ProviderException("Unsupported property.");
                        }
                        svc.Add(pv);
                    }
                }
            }
            catch
            {
                throw new ProviderException("GetPropertyValues");
            }

            UpdateActivityDates(username);
            return svc;
        }


        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection ppvc)
        {
            string username = (string)context["UserName"];
            SettingsPropertyValueCollection svc = new SettingsPropertyValueCollection();

            try
            {
                using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                {
                    var profile = adc.Profile.Where(p => p.Member.Username.ToLower() == username.ToLower()).SingleOrDefault();
                    if (profile == null)
                    {
                        var member = adc.Member.Where(m=>m.Username.ToLower() == username.ToLower()).SingleOrDefault();
                        if(member == null) throw new ProviderException("SetPropertyValues");
                        profile = new Profile();
                        profile.UserId = member.UserId;
                        profile.LastActivityDate = DateTime.Now;
                        profile.LastUpdatedDate = DateTime.Now;
                        adc.Profile.InsertOnSubmit(profile);
                    }
                    foreach (SettingsPropertyValue pv in ppvc)
                    {
                        switch (pv.Property.Name)
                        {
                            case "Cognome":
                                profile.Cognome = (string)pv.PropertyValue;
                                break;
                            case "Nome":
                                profile.Nome = (string)pv.PropertyValue;
                                break;
                            case "Email":
                                profile.Email = (string)pv.PropertyValue;
                                break;
                            default:
                                throw new ProviderException("Unsupported property.");
                        }
                    }
                    adc.SubmitChanges();
                }
            }
            catch
            {
                throw new ProviderException("GetPropertyValues");
            }

            UpdateActivityDates(username);
        }

        private void UpdateActivityDates(string username)
        {
            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                try
                {
                    var profile = adc.Profile.Where(pr => pr.Member.Username.ToLower() == username.ToLower()).SingleOrDefault();
                    if (profile != null)
                    {
                        profile.LastActivityDate = DateTime.Now;
                        adc.SubmitChanges();
                    }
                }
                catch
                {
                    throw new ProviderException("UpdateActivityDates");
                }
            }
        }

        private void CreateProfileForUser(string username, bool isAuthenticated)
        {
            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                try
                {
                    var member = adc.Member.Where(m => m.Username.ToLower() == username.ToLower()).SingleOrDefault();
                    if (member == null) throw new ArgumentException("CreateProfileForUser username is invalid");

                    var newProfile = new Profile();
                    newProfile.LastUpdatedDate = DateTime.UtcNow;
                    adc.Profile.InsertOnSubmit(newProfile);
                    adc.SubmitChanges();
                }
                catch
                {
                    throw new ProviderException("CreateProfileForUser");
                }
            }
        }

        public override int DeleteProfiles(ProfileInfoCollection profiles)
        {
            int deleteCount = 0;
            try
            {
                using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                {
                    foreach (ProfileInfo p in profiles)
                    {
                        var profile = adc.Profile.Where(pr => pr.Member.Username.ToLower() == p.UserName.ToLower()).SingleOrDefault();
                        if (profile != null)
                        {
                            adc.Profile.DeleteOnSubmit(profile);
                        }
                    }
                    adc.SubmitChanges();
                }
            }
            catch
            {
                throw new ProviderException("DeleteProfiles");
            }
            return deleteCount;
        }

        public override int DeleteProfiles(string[] usernames)
        {
            int deleteCount = 0;
            try
            {
                using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                {
                    foreach (string user in usernames)
                    {
                        var profile = adc.Profile.Where(pr => pr.Member.Username.ToLower() == user.ToLower()).SingleOrDefault();
                        if (profile != null)
                        {
                            adc.Profile.DeleteOnSubmit(profile);
                        }
                    }
                    adc.SubmitChanges();
                }
            }
            catch
            {
                throw new ProviderException("DeleteProfiles");
            }
            return deleteCount;
        }

        public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            string[] usernames;
            try
            {
                using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                {
                    var profiles = adc.Profile.Where(p => p.LastActivityDate <= userInactiveSinceDate);
                    usernames = profiles.Select(p => p.Member.Username).ToArray();
                }
            }
            catch
            {
                throw new ProviderException("DeleteInactiveProfiles");
            }
            return DeleteProfiles(usernames);
        }

        public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            CheckParameters(pageIndex, pageSize);

            return GetProfileInfo(authenticationOption, usernameToMatch, null, pageIndex, pageSize, out totalRecords);
        }

        public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            CheckParameters(pageIndex, pageSize);

            return GetProfileInfo(authenticationOption, usernameToMatch, userInactiveSinceDate, pageIndex, pageSize, out totalRecords);
        }

        public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
        {
            CheckParameters(pageIndex, pageSize);

            return GetProfileInfo(authenticationOption, null, null, pageIndex, pageSize, out totalRecords);
        }

        public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            CheckParameters(pageIndex, pageSize);
            return GetProfileInfo(authenticationOption, null, userInactiveSinceDate, pageIndex, pageSize, out totalRecords);
        }


        public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            int inactiveProfiles = 0;

            ProfileInfoCollection profiles = GetProfileInfo(authenticationOption, null, userInactiveSinceDate, 0, 0, out inactiveProfiles);

            return inactiveProfiles;
        }

        private void CheckParameters(int pageIndex, int pageSize)
        {
            if (pageIndex < 0)
                throw new ArgumentException("Page index must 0 or greater.");
            if (pageSize < 1)
                throw new ArgumentException("Page size must be greater than 0.");
        }

        private ProfileInfoCollection GetProfileInfo(ProfileAuthenticationOption authenticationOption, string usernameToMatch, object userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            ProfileInfoCollection profiles = new ProfileInfoCollection();
            try
            {
                using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                {
                    var profs = adc.Profile.Where(p => 1 == 1);
                    // If searching for a user name to match, add the command text and parameters.

                    if (!string.IsNullOrEmpty(usernameToMatch))
                    {
                        profs.Where(p => p.Member.Username.ToLower().Contains(usernameToMatch.ToLower()));
                    }

                    if (userInactiveSinceDate != null)
                    {
                        profs.Where(p => p.LastActivityDate <= (DateTime)userInactiveSinceDate);
                    }


                    totalRecords = profs.Count();
                    // No profiles found.
                    if (totalRecords <= 0) { return profiles; }
                    // Count profiles only.
                    if (pageSize == 0) { return profiles; }

                    int startIndex = pageSize * (pageIndex - 1);
                    int endIndex = startIndex + pageSize - 1;

                    var result = profs.Skip(pageSize * (pageIndex - 1)).Take(pageSize);

                    foreach (var r in result)
                    {
                        ProfileInfo p = new ProfileInfo(r.Member.Username, false, r.LastActivityDate, r.LastUpdatedDate, 0);
                        profiles.Add(p);
                    }
                }
            }
            catch
            {
                throw new ProviderException("GetProfileInfo");
            }
            return profiles;
        }
    }
}
